package com.zmc.coupon.interceptor;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.shardingsphere.sharding.yaml.config.rule.YamlTableRuleConfiguration;
import org.apache.shardingsphere.sharding.yaml.config.strategy.sharding.YamlShardingStrategyConfiguration;

import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Author：zmc
 * @Date：2025/2/12 13:36
 */
@Slf4j
@Intercepts({
        @Signature(type = StatementHandler.class, method = MyBatisShardingInterceptor.PARAMETERIZE_HANDLER_METHOD, args = {Statement.class}),
})
public class MyBatisShardingInterceptor implements Interceptor {

    public static final String PARAMETERIZE_HANDLER_METHOD = "parameterize";

    private final Map<String, YamlTableRuleConfiguration> shardTableMap;

    public MyBatisShardingInterceptor(Map<String, YamlTableRuleConfiguration> shardTableMap) {
        this.shardTableMap = shardTableMap;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (null == shardTableMap || shardTableMap.isEmpty()) {
            return invocation.proceed();
        }
        String method = invocation.getMethod().getName();
        if(PARAMETERIZE_HANDLER_METHOD.equals(method)){
            return parameterize(invocation);
        }
        return invocation.proceed();
    }

    /**
     * 拦截sql参数设置
     * 处理分表属性的设置
     */
    @SneakyThrows
    private Object parameterize(Invocation invocation) {
        log.debug("parameterize");
        StatementHandler routingStatementHandler = (StatementHandler) invocation.getTarget();
        ParameterHandler parameterHandler = routingStatementHandler.getParameterHandler();
        // 通过反射获取BoundSql,MappedStatement对象
        BoundSql boundSql = routingStatementHandler.getBoundSql();
        Field mappedStatementField = parameterHandler.getClass().getDeclaredField("mappedStatement");
        mappedStatementField.setAccessible(true);
        MappedStatement mappedStatement = (MappedStatement) mappedStatementField.get(parameterHandler);
        // 判断是否为更新方法
        if (SqlCommandType.UPDATE != mappedStatement.getSqlCommandType()) {
            return invocation.proceed();
        }
        String msId = mappedStatement.getId();
        String methodName = msId.substring(msId.lastIndexOf(StringPool.DOT) + 1);
        if(!"updateById".equals(methodName)){
            log.debug("parameterize methodName:{} out", methodName);
            return invocation.proceed();
        }
        // 由于拦截器的本质是一个拦截器列表循环执行,所以需要重新获取相关数据
        Class<?> pojoClazz = mappedStatement.getParameterMap().getType();
        if (!pojoClazz.isAnnotationPresent(TableName.class)) {
            log.debug("parameterize {} 缺少 @TableName", pojoClazz.getName());
            return invocation.proceed();
        }
        TableName annotation = pojoClazz.getAnnotation(TableName.class);
        if (!shardTableMap.containsKey(annotation.value())) {
            log.debug("parameterize {} 缺少 @TableName", pojoClazz.getName());
            return invocation.proceed();
        }
        YamlTableRuleConfiguration shardConfig = shardTableMap.get(annotation.value());
        YamlShardingStrategyConfiguration tableStrategy = shardConfig.getTableStrategy();
        List<String> list = new ArrayList<>();
        if(tableStrategy.getStandard() != null){
            String shardingColumn = tableStrategy.getStandard().getShardingColumn();
            if (shardingColumn != null){
                list.add(shardingColumn);
            }
        } else if(tableStrategy.getComplex() != null){
            List<String> collect = Arrays.stream(tableStrategy.getComplex().getShardingColumns().split(",")).collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(collect)){
                list.addAll(collect);
            }
        }
        list.stream().filter(column -> !"id".equals(column)).forEach(shardingColumn->{
            String camelCase = StrUtil.toCamelCase(shardingColumn);
            ParameterMapping parameterMapping = boundSql.getParameterMappings().stream().filter(i -> i.getProperty().equals("et." + camelCase)).findFirst().get();
            boundSql.getParameterMappings().remove(parameterMapping);
        });
        return invocation.proceed();
    }
}
